/*
 * Copyright 2013 Nikolay Todorov
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.jobinsight;
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.RowFilter;
import javax.swing.ScrollPaneConstants;
import javax.swing.border.Border;
import javax.swing.border.EtchedBorder;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.event.MouseInputAdapter;
import javax.swing.table.TableRowSorter;

import com.jobinsight.gui.HintTextField;
import com.jobinsight.util.ObjReader;
import com.jobinsight.util.ObjectType;


public class Viewer {
	private JFrame frame;
	private JPanel contentPanel;
	private JLabel title;
	private JTextField filter;
	private JTable table;
	private ViewerTableModel tableModel;
	private TableRowSorter<ViewerTableModel> tableSorter = null;
	private Point parentLocation = null;
	
	/**
	 * Launch the application.
	 */
	public static void main(String[] args) {
		EventQueue.invokeLater(new Runnable() {
			public void run() {
				try {
					ObjReader reader = new ObjReader();
					Object objToDisplay = reader.getObject();
					if(objToDisplay!=null)
						createViewer( null, reader.getFile().getCanonicalPath(), objToDisplay);
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		});
	}

	/**
	 * Create the application.
	 */
	public Viewer(Point parentLocation) {
		this.parentLocation = parentLocation; 
		initialize();
	}

	/**
	 * Initialize the contents of the frame.
	 */
	private void initialize() {
		frame = new JFrame();
		frame.setBounds(100,100, 450, 300);
		
		if(parentLocation!=null) // move relatively from parent
			frame.setLocation(parentLocation.x + 20, parentLocation.y + 25);

		frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
		
		contentPanel = new JPanel(new BorderLayout(0,0));
		frame.getContentPane().add(contentPanel, BorderLayout.CENTER);
		
		Box titleBox = Box.createVerticalBox();
		
		titleBox.add(title  = new JLabel());
		titleBox.add(filter = new HintTextField("Search ..."));
		contentPanel.add(titleBox,BorderLayout.NORTH);
		
		// apply filter on text change 
		filter.getDocument().addDocumentListener(new DocumentListener() {
			public void changedUpdate(DocumentEvent e) {
				filterTableContent();
			}

			public void insertUpdate(DocumentEvent e) {
				filterTableContent();
			}

			public void removeUpdate(DocumentEvent e) {
				filterTableContent();
			}
		});

		table = new JTable(tableModel = new ViewerTableModel());
		table.setAutoResizeMode(JTable.AUTO_RESIZE_SUBSEQUENT_COLUMNS);
		table.getColumnModel().getColumn(0).setMinWidth(50);
		table.getColumnModel().getColumn(0).setPreferredWidth(150);
		table.getColumnModel().getColumn(1).setPreferredWidth(300);

		tableSorter = new TableRowSorter<ViewerTableModel>(tableModel);
		table.setRowSorter(tableSorter);

		ViewerCellRenderer.setDefaultRender(table);
		contentPanel.add(new JScrollPane(table),BorderLayout.CENTER);
		
		// open new viewer on row click
		table.addMouseListener(
			new MouseAdapter(){
				public void mouseClicked(MouseEvent e){
					if (e.getClickCount() == 2){
						int selectedRow = table.getSelectedRow();
						if(selectedRow != -1)
							openViewerForSelectedObject();
					}
		      	}
		    } 
		);
		// set hand cursor on non primitive objects
		table.addMouseMotionListener(
			new MouseInputAdapter() {
				public void mouseMoved(MouseEvent e){
					setCursor(e.getPoint());
				}
			}
		);
	}

	public void setCursor(Point mouseOver){
		boolean handCursor = !ObjectType.isPrimitive( getMouseOverObject(mouseOver) );

		if(handCursor)
			table.setCursor(java.awt.Cursor.getPredefinedCursor(java.awt.Cursor.HAND_CURSOR));
		else
			table.setCursor(java.awt.Cursor.getDefaultCursor());
	}
	
	private Object getMouseOverObject(Point mouseOver){
		int mouseOverRowId = table.rowAtPoint(mouseOver);
		if(mouseOverRowId !=-1 )
			return tableModel.getObjectAtRow(table.convertRowIndexToModel(mouseOverRowId));
		else
			return null;
	}
	
	private Object getSelectedObject(){
		int selectedRow = table.getSelectedRow();
		if(selectedRow != -1){
			int modelRow = table.convertRowIndexToModel(selectedRow);
			return tableModel.getObjectAtRow(modelRow);
		}
		return null;
	}
	
	/**
	 * apply the new filter content
	 */
	private void filterTableContent() {
		RowFilter<ViewerTableModel, Object> rf = null; 
		try {
			List<RowFilter<Object,Object>> filters = new ArrayList<RowFilter<Object,Object>>(2);
			filters.add(RowFilter.regexFilter(filter.getText(), 0));
			filters.add(RowFilter.regexFilter(filter.getText(), 1));
			rf = RowFilter.orFilter(filters);

		} catch (java.util.regex.PatternSyntaxException e) {
			return;
		}
		tableSorter.setRowFilter(rf);
	}

	public void openViewerForSelectedObject(){
		Object objectAtRow = getSelectedObject();
		if(ObjectType.isPrimitive(objectAtRow))
			return; //ignore primitive objects
		
		createViewer(this , objectAtRow);
	}
	
	public void displayData(Object theObject){
		if(theObject==null)return;
		
		// set title
		title.setText(createObjectInfoPanel(theObject));
		
		List<ViewerData> fieldsList = null;
		if(theObject instanceof Iterable){
			fieldsList = getDataForIterable((Iterable) theObject);
		} else if(theObject instanceof Map){
			fieldsList = getDataForMap((Map) theObject);
		} else {
			fieldsList = getDataForObject(theObject);
		}
		
		filter.requestFocus();

		tableModel.setData(fieldsList);
	}
	
	private List<Field> getFields( Class theClass ) {
		List<Field> fieldsList = new ArrayList<Field>();
		if(theClass.getSuperclass()!=null ){
			fieldsList.addAll( getFields( theClass.getSuperclass() ));
		}
		Field[] fields = theClass.getDeclaredFields();
		fieldsList.addAll( Arrays.asList(fields) );
	    return fieldsList;
	}
	
	private List<ViewerData> getDataForObject( Object theObject) {
		List<ViewerData> result = new ArrayList();
		
	    List fieldsList = getFields( theObject.getClass() );

	    Collections.sort(
		    fieldsList,
		    new Comparator() {
		        public int compare(Object o1, Object o2) {
		            return (((Field) o1).getName()).compareTo(((Field) o2).getName());
		        }
		    }
	    );
	    
	    Iterator fieldsIt = fieldsList.iterator();
	    while (fieldsIt.hasNext()) {
	    	Field f = (Field) fieldsIt.next();
		      Class type = f.getType();
		      String name = f.getName();
		      
		      try {
		    	  f.setAccessible(true);
		    	  Object value = f.get(theObject);
		    	  result.add(createValueData(name, value));
		      } catch (Exception e) {
					e.printStackTrace();
		      }
		}
	    return result;
	}

	public List<ViewerData> getDataForMap(Map theObject){
		List<ViewerData> result = new ArrayList();
		Map sortedMap = theObject;
		try{
			sortedMap = new TreeMap(theObject);
		}catch(Throwable e){
			// may be the keys are not Comparable
		}
		Iterator keysIt = sortedMap.keySet().iterator();
		while (keysIt.hasNext()) {
			Object name = (Object) keysIt.next();
			Object value = sortedMap.get(name); 
			result.add(createValueData(name.toString(), value));
		}
		return result;
	}
	
	public List<ViewerData> getDataForIterable(Iterable theObject){
		List<ViewerData> result = new ArrayList();
		Iterator keysIt = theObject.iterator();
		while (keysIt.hasNext()) {
			Object value = (Object) keysIt.next(); 
			Object name = value.getClass().getCanonicalName(); 
			result.add(createValueData(name.toString(), value));
		}
		return result;
	}
	
	String createObjectInfoPanel(Object value){
		return value.getClass().getCanonicalName();
	}
	
	ViewerData createValueData(String name,  Object object){
		return new ViewerData(object,name,object!=null?object.toString():"null");
	}
	
	private Point getLocation() {
		return frame.getLocation();
	}

	public static void createViewer(Viewer parent, Object  objToDisplay){
		createViewer( parent.getLocation(), null, objToDisplay);
	}
	

	public static void createViewer(Point parentLocation, String title, Object objToDisplay){
		if(objToDisplay==null)
			return; 
		
		Viewer window = new Viewer(parentLocation);
		window.displayData(objToDisplay);
		
		window.frame.setTitle(title!=null?title:objToDisplay.toString());
		window.frame.setVisible(true);
	}

}
